/*!
 * # Fomantic-UI - Accordion
 * https://github.com/fomantic/Fomantic-UI/
 *
 *
 * Released under the MIT license
 * https://opensource.org/licenses/MIT
 *
 */

(function ($, window, document) {
    'use strict';

    function isFunction(obj) {
        return typeof obj === 'function' && typeof obj.nodeType !== 'number';
    }

    window = window !== undefined && window.Math === Math
        ? window
        : globalThis;

    $.fn.accordion = function (parameters) {
        var
            $allModules     = $(this),

            time            = Date.now(),
            performance     = [],

            query           = arguments[0],
            methodInvoked   = typeof query === 'string',
            queryArguments  = [].slice.call(arguments, 1),

            returnedValue
        ;
        $allModules.each(function () {
            var
                settings        = $.isPlainObject(parameters)
                    ? $.extend(true, {}, $.fn.accordion.settings, parameters)
                    : $.extend({}, $.fn.accordion.settings),

                className       = settings.className,
                namespace       = settings.namespace,
                selector        = settings.selector,
                error           = settings.error,

                eventNamespace  = '.' + namespace,
                moduleNamespace = 'module-' + namespace,

                $module  = $(this),
                $title   = $module.find(selector.title),
                $content = $module.find(selector.content),

                element  = this,
                instance = $module.data(moduleNamespace),
                observer,
                module
            ;

            module = {

                initialize: function () {
                    module.debug('Initializing', $module);
                    module.bind.events();
                    if (settings.observeChanges) {
                        module.observeChanges();
                    }
                    module.instantiate();
                },

                instantiate: function () {
                    instance = module;
                    $module
                        .data(moduleNamespace, module)
                    ;
                },

                destroy: function () {
                    module.debug('Destroying previous instance', $module);
                    $module
                        .off(eventNamespace)
                        .removeData(moduleNamespace)
                    ;
                },

                refresh: function () {
                    $title = $module.find(selector.title);
                    $content = $module.find(selector.content);
                },

                observeChanges: function () {
                    if ('MutationObserver' in window) {
                        observer = new MutationObserver(function (mutations) {
                            module.debug('DOM tree modified, updating selector cache');
                            module.refresh();
                        });
                        observer.observe(element, {
                            childList: true,
                            subtree: true,
                        });
                        module.debug('Setting up mutation observer', observer);
                    }
                },

                bind: {
                    events: function () {
                        module.debug('Binding delegated events');
                        $module
                            .on(settings.on + eventNamespace, selector.trigger, module.event.click)
                        ;
                    },
                },

                event: {
                    click: function (event) {
                        if ($(event.target).closest(selector.ignore).length === 0) {
                            module.toggle.call(this);
                        }
                    },
                },

                toggle: function (query) {
                    var
                        $activeTitle = query !== undefined
                            ? (typeof query === 'number'
                                ? $title.eq(query)
                                : $(query).closest(selector.title))
                            : $(this).closest(selector.title),
                        $activeContent = $activeTitle.next($content),
                        isAnimating = $activeContent.hasClass(className.animating),
                        isActive    = $activeContent.hasClass(className.active),
                        isOpen      = isActive && !isAnimating,
                        isOpening   = !isActive && isAnimating
                    ;
                    module.debug('Toggling visibility of content', $activeTitle);
                    if (isOpen || isOpening) {
                        if (settings.collapsible) {
                            module.close.call($activeTitle);
                        } else {
                            module.debug('Cannot close accordion content collapsing is disabled');
                        }
                    } else {
                        module.open.call($activeTitle);
                    }
                },

                open: function (query) {
                    var
                        $activeTitle = query !== undefined
                            ? (typeof query === 'number'
                                ? $title.eq(query)
                                : $(query).closest(selector.title))
                            : $(this).closest(selector.title),
                        $activeContent = $activeTitle.next($content),
                        isAnimating = $activeContent.hasClass(className.animating),
                        isActive    = $activeContent.hasClass(className.active),
                        isOpen      = isActive || isAnimating
                    ;
                    if (isOpen) {
                        module.debug('Accordion already open, skipping', $activeContent);

                        return;
                    }
                    module.debug('Opening accordion content', $activeTitle);
                    settings.onOpening.call($activeContent);
                    settings.onChanging.call($activeContent);
                    if (settings.exclusive) {
                        module.closeOthers.call($activeTitle);
                    }
                    $activeTitle
                        .addClass(className.active)
                    ;
                    $activeContent
                        .stop(true, true)
                        .addClass(className.animating)
                    ;
                    if (settings.animateChildren) {
                        if ($.fn.transition !== undefined) {
                            $activeContent
                                .children()
                                .transition({
                                    animation: 'fade in',
                                    queue: false,
                                    useFailSafe: true,
                                    debug: settings.debug,
                                    verbose: settings.verbose,
                                    silent: settings.silent,
                                    duration: settings.duration,
                                    skipInlineHidden: true,
                                    onComplete: function () {
                                        $activeContent.children().removeClass(className.transition);
                                    },
                                })
                            ;
                        } else {
                            $activeContent
                                .children()
                                .stop(true, true)
                                .animate({
                                    opacity: 1,
                                }, settings.duration, module.resetOpacity);
                        }
                    }
                    $activeContent
                        .slideDown(settings.duration, settings.easing, function () {
                            $activeContent
                                .removeClass(className.animating)
                                .addClass(className.active)
                            ;
                            module.reset.display.call(this);
                            settings.onOpen.call(this);
                            settings.onChange.call(this);
                        })
                    ;
                },

                close: function (query) {
                    var
                        $activeTitle = query !== undefined
                            ? (typeof query === 'number'
                                ? $title.eq(query)
                                : $(query).closest(selector.title))
                            : $(this).closest(selector.title),
                        $activeContent = $activeTitle.next($content),
                        isAnimating    = $activeContent.hasClass(className.animating),
                        isActive       = $activeContent.hasClass(className.active),
                        isOpening      = !isActive && isAnimating,
                        isClosing      = isActive && isAnimating
                    ;
                    if ((isActive || isOpening) && !isClosing) {
                        module.debug('Closing accordion content', $activeContent);
                        settings.onClosing.call($activeContent);
                        settings.onChanging.call($activeContent);
                        $activeTitle
                            .removeClass(className.active)
                        ;
                        $activeContent
                            .stop(true, true)
                            .addClass(className.animating)
                        ;
                        if (settings.animateChildren) {
                            if ($.fn.transition !== undefined) {
                                $activeContent
                                    .children()
                                    .transition({
                                        animation: 'fade out',
                                        queue: false,
                                        useFailSafe: true,
                                        debug: settings.debug,
                                        verbose: settings.verbose,
                                        silent: settings.silent,
                                        duration: settings.duration,
                                        skipInlineHidden: true,
                                    })
                                ;
                            } else {
                                $activeContent
                                    .children()
                                    .stop(true, true)
                                    .animate({
                                        opacity: 0,
                                    }, settings.duration, module.resetOpacity);
                            }
                        }
                        $activeContent
                            .slideUp(settings.duration, settings.easing, function () {
                                $activeContent
                                    .removeClass(className.animating)
                                    .removeClass(className.active)
                                ;
                                module.reset.display.call(this);
                                settings.onClose.call(this);
                                settings.onChange.call(this);
                            })
                        ;
                    }
                },

                closeOthers: function (index) {
                    var
                        $activeTitle = index !== undefined
                            ? $title.eq(index)
                            : $(this).closest(selector.title),
                        $parentTitles    = $activeTitle.parents(selector.content).prev(selector.title),
                        $activeAccordion = $activeTitle.closest(selector.accordion),
                        activeSelector   = selector.title + '.' + className.active + ':visible',
                        activeContent    = selector.content + '.' + className.active + ':visible',
                        $openTitles,
                        $nestedTitles,
                        $openContents
                    ;
                    if (settings.closeNested) {
                        $openTitles = $activeAccordion.find(activeSelector).not($parentTitles);
                        $openContents = $openTitles.next($content);
                    } else {
                        $openTitles = $activeAccordion.find(activeSelector).not($parentTitles);
                        $nestedTitles = $activeAccordion.find(activeContent).find(activeSelector).not($parentTitles);
                        $openTitles = $openTitles.not($nestedTitles);
                        $openContents = $openTitles.next($content);
                    }
                    if ($openTitles.length > 0) {
                        module.debug('Exclusive enabled, closing other content', $openTitles);
                        $openTitles
                            .removeClass(className.active)
                        ;
                        $openContents
                            .removeClass(className.animating)
                            .stop(true, true)
                        ;
                        if (settings.animateChildren) {
                            if ($.fn.transition !== undefined) {
                                $openContents
                                    .children()
                                    .transition({
                                        animation: 'fade out',
                                        useFailSafe: true,
                                        debug: settings.debug,
                                        verbose: settings.verbose,
                                        silent: settings.silent,
                                        duration: settings.duration,
                                        skipInlineHidden: true,
                                    })
                                ;
                            } else {
                                $openContents
                                    .children()
                                    .stop(true, true)
                                    .animate({
                                        opacity: 0,
                                    }, settings.duration, module.resetOpacity);
                            }
                        }
                        $openContents
                            .slideUp(settings.duration, settings.easing, function () {
                                $(this).removeClass(className.active);
                                module.reset.display.call(this);
                            })
                        ;
                    }
                },

                reset: {

                    display: function () {
                        module.verbose('Removing inline display from element', this);
                        var $element = $(this);
                        $element.css('display', '');
                        if ($element.attr('style') === '') {
                            $element
                                .attr('style', '')
                                .removeAttr('style')
                            ;
                        }
                    },

                    opacity: function () {
                        module.verbose('Removing inline opacity from element', this);
                        var $element = $(this);
                        $element.css('opacity', '');
                        if ($element.attr('style') === '') {
                            $element
                                .attr('style', '')
                                .removeAttr('style')
                            ;
                        }
                    },

                },

                setting: function (name, value) {
                    module.debug('Changing setting', name, value);
                    if ($.isPlainObject(name)) {
                        $.extend(true, settings, name);
                    } else if (value !== undefined) {
                        if ($.isPlainObject(settings[name])) {
                            $.extend(true, settings[name], value);
                        } else {
                            settings[name] = value;
                        }
                    } else {
                        return settings[name];
                    }
                },
                internal: function (name, value) {
                    module.debug('Changing internal', name, value);
                    if (value !== undefined) {
                        if ($.isPlainObject(name)) {
                            $.extend(true, module, name);
                        } else {
                            module[name] = value;
                        }
                    } else {
                        return module[name];
                    }
                },
                debug: function () {
                    if (!settings.silent && settings.debug) {
                        if (settings.performance) {
                            module.performance.log(arguments);
                        } else {
                            module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
                            module.debug.apply(console, arguments);
                        }
                    }
                },
                verbose: function () {
                    if (!settings.silent && settings.verbose && settings.debug) {
                        if (settings.performance) {
                            module.performance.log(arguments);
                        } else {
                            module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
                            module.verbose.apply(console, arguments);
                        }
                    }
                },
                error: function () {
                    if (!settings.silent) {
                        module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
                        module.error.apply(console, arguments);
                    }
                },
                performance: {
                    log: function (message) {
                        var
                            currentTime,
                            executionTime,
                            previousTime
                        ;
                        if (settings.performance) {
                            currentTime = Date.now();
                            previousTime = time || currentTime;
                            executionTime = currentTime - previousTime;
                            time = currentTime;
                            performance.push({
                                Name: message[0],
                                Arguments: [].slice.call(message, 1) || '',
                                Element: element,
                                'Execution Time': executionTime,
                            });
                        }
                        clearTimeout(module.performance.timer);
                        module.performance.timer = setTimeout(function () { module.performance.display(); }, 500);
                    },
                    display: function () {
                        var
                            title = settings.name + ':',
                            totalTime = 0
                        ;
                        time = false;
                        clearTimeout(module.performance.timer);
                        $.each(performance, function (index, data) {
                            totalTime += data['Execution Time'];
                        });
                        title += ' ' + totalTime + 'ms';
                        if (performance.length > 0) {
                            console.groupCollapsed(title);
                            if (console.table) {
                                console.table(performance);
                            } else {
                                $.each(performance, function (index, data) {
                                    console.log(data.Name + ': ' + data['Execution Time'] + 'ms');
                                });
                            }
                            console.groupEnd();
                        }
                        performance = [];
                    },
                },
                invoke: function (query, passedArguments, context) {
                    var
                        object = instance,
                        maxDepth,
                        found,
                        response
                    ;
                    passedArguments = passedArguments || queryArguments;
                    context = context || element;
                    if (typeof query === 'string' && object !== undefined) {
                        query = query.split(/[ .]/);
                        maxDepth = query.length - 1;
                        $.each(query, function (depth, value) {
                            var camelCaseValue = depth !== maxDepth
                                ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
                                : query;
                            if ($.isPlainObject(object[camelCaseValue]) && (depth !== maxDepth)) {
                                object = object[camelCaseValue];
                            } else if (object[camelCaseValue] !== undefined) {
                                found = object[camelCaseValue];

                                return false;
                            } else if ($.isPlainObject(object[value]) && (depth !== maxDepth)) {
                                object = object[value];
                            } else if (object[value] !== undefined) {
                                found = object[value];

                                return false;
                            } else {
                                module.error(error.method, query);

                                return false;
                            }
                        });
                    }
                    if (isFunction(found)) {
                        response = found.apply(context, passedArguments);
                    } else if (found !== undefined) {
                        response = found;
                    }
                    if (Array.isArray(returnedValue)) {
                        returnedValue.push(response);
                    } else if (returnedValue !== undefined) {
                        returnedValue = [returnedValue, response];
                    } else if (response !== undefined) {
                        returnedValue = response;
                    }

                    return found;
                },
            };
            if (methodInvoked) {
                if (instance === undefined) {
                    module.initialize();
                }
                module.invoke(query);
            } else {
                if (instance !== undefined) {
                    instance.invoke('destroy');
                }
                module.initialize();
            }
        });

        return returnedValue !== undefined
            ? returnedValue
            : this;
    };

    $.fn.accordion.settings = {

        name: 'Accordion',
        namespace: 'accordion',

        silent: false,
        debug: false,
        verbose: false,
        performance: true,

        on: 'click', // event on title that opens accordion

        observeChanges: true, // whether accordion should automatically refresh on DOM insertion

        exclusive: true, // whether a single accordion content panel should be open at once
        collapsible: true, // whether accordion content can be closed
        closeNested: false, // whether nested content should be closed when a panel is closed
        animateChildren: true, // whether children opacity should be animated

        duration: 350, // duration of animation
        easing: 'easeOutQuad', // easing equation for animation

        onOpening: function () {}, // callback before open animation
        onClosing: function () {}, // callback before closing animation
        onChanging: function () {}, // callback before closing or opening animation

        onOpen: function () {}, // callback after open animation
        onClose: function () {}, // callback after closing animation
        onChange: function () {}, // callback after closing or opening animation

        error: {
            method: 'The method you called is not defined',
        },

        className: {
            active: 'active',
            animating: 'animating',
            transition: 'transition',
        },

        selector: {
            accordion: '.accordion',
            title: '.title',
            trigger: '.title',
            ignore: '.ui.dropdown',
            content: '.content',
        },

    };

    // Adds easing
    $.extend($.easing, {
        easeOutQuad: function (x) {
            return 1 - (1 - x) * (1 - x);
        },
    });
})(jQuery, window, document);
